﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using DiscoveryLogic.Common.Utilities;
using DiscoveryLogic.DataViz.NetworkDiagram.LinkerControls;
using DiscoveryLogic.DataViz.NetworkDiagram.NetworkData;

namespace DiscoveryLogic.DataViz.NetworkDiagram.Rules
{
    /// <summary>
    /// 1. create a new network using db relations
    /// 2. dedup entities
    /// </summary>
    public class EntityRelationWorker
    {
        #region fields
        private Network _SrcNetwork;
        private EntityRelationRule _Rule;
        private DataTable _SrcDataTable;
        private string _NewNetworkName;
        #endregion

        #region ctor
        /// <summary>
        /// 
        /// </summary>
        /// <param name="rule"></param>
        /// <param name="srcNetwork"></param>
        /// <param name="newNetworkName"></param>
        public EntityRelationWorker(EntityRelationRule rule, Network srcNetwork, string newNetworkName)
        {
            this._Rule = rule;
            this._SrcNetwork = srcNetwork;
            this._SrcDataTable=new DataTable();
            List<LinkDataObject> srcLinks=new List<LinkDataObject>();
            Network.ReadData(this._SrcNetwork.NetworkName, ref this._SrcDataTable, ref srcLinks);
            this._NewNetworkName = newNetworkName;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="rule"></param>
        /// <param name="srcNetwork"></param>
        /// <param name="srcKeys"></param>
        public EntityRelationWorker(EntityRelationRule rule, Network srcNetwork, List<string> srcKeys)
        {
            this._Rule = rule;
            this._SrcNetwork = srcNetwork;
            this._SrcDataTable = new DataTable();
            this._SrcDataTable = this._SrcNetwork.SelectByKey(srcKeys);
        }
        #endregion

        #region new network
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Network ReadRelatedData()
        {
            Network newNetwork=new Network();
            List<BaseEntity> newEntities=new List<BaseEntity>();
            List<LinkDataObject> newLinks=new List<LinkDataObject>();

            try
            {
                List<string> sqlParamNames=new List<string>();
                Regex paramRegex = new Regex(@"@([^\s,=\)]+)", RegexOptions.IgnoreCase);
                Match paramMatch = paramRegex.Match(this._Rule.Sql);
                while(paramMatch.Success)
                {
                    string paramName = paramMatch.Groups[1].Value;
                    if(!sqlParamNames.Contains(paramName))
                        sqlParamNames.Add(paramName);

                    paramMatch = paramMatch.NextMatch();
                }
                PropertyInfo[] srcProps = this._Rule.SrcEntityType.GetProperties();
                Dictionary<string,PropertyInfo> srcPropsByName=new Dictionary<string, PropertyInfo>();
                foreach(PropertyInfo prop in srcProps)
                {
                    srcPropsByName.Add(prop.Name,prop);
                }
                PropertyInfo[] tgtProps = this._Rule.TgtEntityType.GetProperties();
                Dictionary<string,PropertyInfo> tgtPropsByName=new Dictionary<string, PropertyInfo>();
                foreach(PropertyInfo prop in tgtProps)
                {
                    tgtPropsByName.Add(prop.Name, prop);
                }

                foreach(DataRow drSrc in this._SrcDataTable.Rows)
                {
                    BaseEntity srcEntity = (BaseEntity) Activator.CreateInstance(
                        this._Rule.SrcEntityType, this._Rule.SrcEntityIDField, this._Rule.SrcEntityNameField);
                    string sql = this._Rule.Sql;

                    for(int i=0;i<this._SrcDataTable.Columns.Count;i++)
                    {
                        string colName = this._SrcDataTable.Columns[i].ColumnName;
                        if(!this._Rule.FieldNameMappings.ContainsValue(colName))
                            continue;
                        object fieldValue = drSrc[colName];
                        foreach(string propName in this._Rule.FieldNameMappings.Keys)
                        {
                            if(this._Rule.FieldNameMappings[propName]==colName)
                            {
                                if (drSrc[colName] != null && drSrc[colName] != DBNull.Value)
                                {
                                    PropertyInfo prop = srcPropsByName[propName];
                                    SimpleDataType dbType3 = SimpleDataTypeUtil.ToSimpleDataType(prop.PropertyType);
                                    fieldValue = SimpleDataTypeUtil.ReadDbValue(dbType3, fieldValue);
                                    prop.SetValue(srcEntity, fieldValue, null);
                                }
                            }
                        }

                        foreach (string paramName in this._Rule.SqlParameters.Keys)
                        {
                            string propName = this._Rule.SqlParameters[paramName];
                            if (this._Rule.FieldNameMappings[propName] == colName)
                            {
                                SqlDbType dbType = this._Rule.ParameterTypes[paramName];
                                SimpleDataType dbType2 = SimpleDataTypeUtil.ToSimpleDataType(dbType);
                                fieldValue = SimpleDataTypeUtil.ReadDbValue(dbType2, fieldValue);

                                if (fieldValue == null)
                                {
                                    sql = sql.Replace("@" + paramName, "null");
                                }
                                else if (this._Rule.FieldTransformations.ContainsKey(propName))
                                {
                                    object transformedValue = fieldValue;
                                    string sqlValue = FieldDataType.ToMssqlDataValue(
                                        dbType, transformedValue.ToString());
                                    sql = sql.Replace("@" + paramName, sqlValue);
                                }
                                else
                                {
                                    string sqlValue = FieldDataType.ToMssqlDataValue(dbType, fieldValue.ToString());
                                    sql = sql.Replace("@" + paramName, sqlValue);
                                }
                            }
                        }
                    }

                    DataTable dtOutput = DbUtil.GetTableDataBySQL(this._Rule.ConnStr, this._Rule.TgtEntityType.Name, sql);
                    if(dtOutput !=null && dtOutput.Rows.Count>0)
                    {
                        List<BaseEntity> relatedEntities=new List<BaseEntity>();

                        foreach(DataRow drTgt in dtOutput.Rows)
                        {
                            BaseEntity tgtEntity = (BaseEntity) Activator.CreateInstance(
                                this._Rule.TgtEntityType, this._Rule.TgtEntityIDField, this._Rule.TgtEntityNameField);
                            for(int i=0;i<dtOutput.Columns.Count;i++)
                            {
                                string colName = dtOutput.Columns[i].ColumnName;
                                object fieldValue = drTgt[colName];
                                if (fieldValue != null && this._Rule.OutputFieldNameMappings.ContainsKey(colName))
                                {
                                    string propName = this._Rule.OutputFieldNameMappings[colName];
                                    PropertyInfo prop = tgtPropsByName[propName];
                                    SimpleDataType dbType = SimpleDataTypeUtil.ToSimpleDataType(prop.PropertyType);
                                    fieldValue = SimpleDataTypeUtil.ReadDbValue(dbType, fieldValue);
                                    tgtPropsByName[propName].SetValue(tgtEntity, fieldValue, null);
                                }
                            }
                            relatedEntities.Add(tgtEntity);
                        }

                        List<LinkDataObject> links = this.FilterLinkedEntities(srcEntity, ref relatedEntities);
                        if (relatedEntities != null && relatedEntities.Count > 0 && links !=null && links.Count>0)
                        {
                            newEntities.AddRange(relatedEntities);
                            newLinks.AddRange(links);
                        }
                    }
                }
            }
            catch(Exception ex)
            {
                throw new Exception("Unable to read related entities: " + ex.ToString());
            }
            
            if(newEntities==null || newEntities.Count==0)
                throw new Exception("Unable to get related entities");


            if(this._Rule.DedupCalculatorType!=null)
            {
                Dictionary<string, string> collapsedPKs = this.CollapseEntities(newEntities);
                // update linkage 
                for(int i=0;i<newLinks.Count;i++)
                {
                    LinkDataObject link = newLinks[i];
                    if (collapsedPKs.ContainsKey(link.FromKey))
                        link.FromKey = collapsedPKs[link.FromKey];
                    if (collapsedPKs.ContainsKey(link.ToKey))
                        link.ToKey = collapsedPKs[link.ToKey];
                }
                int entityIdx = 0;
                
                while (entityIdx < newEntities.Count)
                {
                    BaseEntity entity = newEntities[entityIdx];
                    string pk = entity.PKValue();
                    if (!collapsedPKs.ContainsValue(pk))
                        newEntities.Remove(entity);
                    else
                        entityIdx++;
                }
            }

            // newNetwork=new Network();

            return newNetwork;
        }
        #endregion

        #region distance
        private List<LinkDataObject> FilterLinkedEntities(BaseEntity srcEntity, ref List<BaseEntity> relatedEntities)
        {
            List<LinkDataObject> links = new List<LinkDataObject>();

            if(relatedEntities !=null && relatedEntities.Count>0)
            {
                int idx = 0;
                IEntityLinker calc =
                    (IEntityLinker)Activator.CreateInstance(this._Rule.EntityLinkerType);
                calc.InitBindings(this._Rule.SrcEntityType, this._Rule.TgtEntityType,
                    this._Rule.LinkCalcArgumentMappings);
                while(idx < relatedEntities.Count)
                {
                    LinkDataObject link = new LinkDataObject(
                        this._SrcNetwork.NetworkName,
                        this._NewNetworkName, srcEntity.Name(), relatedEntities[idx].Name(),
                        srcEntity.PKValue(), relatedEntities[idx].PKValue(), double.PositiveInfinity);
                    double? distance = calc.CalculateDistance(srcEntity, relatedEntities[idx]);
                    if(distance.HasValue && distance <= this._Rule.LinkDistanceThreshold)
                    {
                        link.Distance = distance.Value;
                        links.Add(link);
                        idx++;
                    }
                    else
                    {
                        relatedEntities.Remove(relatedEntities[idx]);
                    }
                }
            }

            return links;
        }
        #endregion

        #region dedup 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entities"></param>
        /// <returns>
        /// return pk -> collapsed pk (lowest pk value)
        /// </returns>
        private Dictionary<string,string> CollapseEntities(List<BaseEntity> entities)
        {
            Dictionary<string, string> collapsedPKs=new Dictionary<string, string>();
            foreach(BaseEntity entity in entities)
            {
                if(!collapsedPKs.ContainsKey(entity.PKValue()))
                    collapsedPKs.Add(entity.PKValue(),entity.PKValue());
            }
            return collapsedPKs;
        }
        #endregion
    }
}
